﻿using System;

namespace tiny_compiler
{
	public static class GameCode
	{
		public static void Generate(Compiler _compiler)
		{
			m_compiler = _compiler;

			Jump("start_label");

			// константы
			ValueNumber("zero", 0);
			ValueNumber("one", 1);
			ValueNumber("minus_one", -1);
			Value("new_line", "\\n");
			ValueNumber("false", 0);
			ValueNumber("true", 1);

			ValueNumber("level_offset", m_width * 10);
			ValueNumber("level_start", m_width);
			ValueNumber("level_end", (m_width * 13) - 2);
			ValueNumber("end_offset", m_width * 24);
			ValueNumber("left_offset", -1);
			ValueNumber("right_offset", 1);
			ValueNumber("top_offset", -m_width);
			ValueNumber("bottom_offset", m_width);

			Value("pers", "8");

			Value("left_move", "a");
			Value("right_move", "d");
			Value("top_move", "w");
			Value("bottom_move", "s");

			Value("left_dig", "A");
			Value("right_dig", "D");
			Value("top_dig", "W");
			Value("bottom_dig", "S");

			Value("1", "1");
			Value("2", "2");
			Value("3", "3");
			Value("4", "4");
			Value("5", "5");

			Value(" ", " ");
			Value("@", "@");
			Value("#", "#");
			Value("+", "+");
			Value("~", "~");
			Value("W", "W");
			Value("w", "w");
			Value(".", ".");

			// переменные
			Value("input_key", " ");
			ValueNumber("pers_position", m_width * 2 + 25);
			ValueNumber("pers_fall", 0);

			// данные
			Value("logo", "     ###`   ##``##``##`  ##``######  ######  ######``######``######`#######    \\n     ###`` ###``##``###``##``##````  ##````  ##``##``#````#``##````````##``    \\n     ###```###``##``###``##``##````  ##````  ##``#```#````#``##````````##`     \\n    `########```##``##`####``######  ##````  #####```##```#``#######```###     \\n    ###`##`##``##``###`####``##````  ##      ##``##``######``###````````##     \\n   `##``#``##``##``##```###``##````  ##      ##``##``###`##```##```  ```##`    \\n   ##``````##``##``##```##```#####`  ######  ##``##``###``##``##``   ```###    \\n   ##`````###``##``##```##````#####  ######  `#``###``##``##``##`    ````##    \\n   `````  ````````````  ```````````  ``````  ``  ```````````````      `````    \\n    ````   ```````````   ``````````  ``````  ``  `` ```````````       ````     ");
			Value("level", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\\n@                                                                             @\\n@             ###+                                     +#########+            @\\n@             ####+                                 +###############+         @\\n@             +++++                                +#####        ####+        @\\n@#            +###+   +##### #+         ++         +####         #########++##@\\n@##~~~~~~~~~~~+###+######### ##+        +#+       +#####          #######  +##@\\n@###~~~~~~~~~~####+######### ###+      +##+ +##########            #####  +###@\\n@###~~~~~~~~~~####+######### ####+    +################                  +####@\\n@######+++########+######### ######+##################   +##~~~~~~~###########@\\n@######+++########+## +####   #####+##############      +#####~~~~############@\\n@#++++++++##    ##+## +###         +   ###########     +######################@\\n@###+++W+++++   +++   +        +#####+                +#######################@\\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");

			// начало кода
			Label("start_label");

			// русуем первоначальные данные
			Print("logo");
			DrawLevel();
			DrawPers();
			Cursor("end_offset");

			// основной цикл
			Label("begin_label");

			// читаем клавишу
			ReadKey("input_key");

			// тут только двигаемся
			UpdateMoving();

			// тут меняем блоки
			UpdateChangeLevel();

			// изменение уровня
			UpdateLevel();

			// рисуем обновленый уровень
			DrawLevel();

			// рисуем позицию перса
			DrawPers();

			Cursor("end_offset");

			// на новуй круг
			Jump("begin_label");

			// выход
			Label("end_label");
		}

		private static void DrawLevel()
		{
			Cursor("level_offset");
			Print("level");
		}

		private static void DrawPers()
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);
			Copy("offset_value" + id, "level_offset");
			Add("offset_value" + id, "pers_position");
			Cursor("offset_value" + id);
			Print("pers");
		}

		private static void UpdateMoving()
		{
			string id = GenerateId();
			Jump("fall_label" + id);
			ValueNumber("result_value" + id, 0);

			// расчет падения
			Label("fall_label" + id);
			IsFall("level", "pers_position", "pers_fall");
			Compare("pers_fall", "false", "left_label" + id);
			Add("pers_position", "bottom_offset");
			Jump("end_label" + id);

			// расчет движения влево
			Label("left_label" + id);
			IsLeftMoving("level", "pers_position", "result_value" + id);
			Compare("result_value" + id, "false", "right_label" + id);
			Add("pers_position", "left_offset");
			Jump("end_label" + id);

			// расчет движения вправо
			Label("right_label" + id);
			IsRightMoving("level", "pers_position", "result_value" + id);
			Compare("result_value" + id, "false", "top_label" + id);
			Add("pers_position", "right_offset");
			Jump("end_label" + id);

			// расчет движения вверх
			Label("top_label" + id);
			IsTopMoving("level", "pers_position", "result_value" + id);
			Compare("result_value" + id, "false", "bottom_label" + id);
			Add("pers_position", "top_offset");
			Jump("end_label" + id);

			// расчет движения вниз
			Label("bottom_label" + id);
			IsBottomMoving("level", "pers_position", "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);
			Add("pers_position", "bottom_offset");

			Label("end_label" + id);
		}

		private static void IsStable(string _level, string _offset, string _result)
		{
			string labelId = GenerateId();

			Compare(_level, "@", "true_label" + labelId, true, _offset);
			Compare(_level, "#", "true_label" + labelId, true, _offset);
			Compare(_level, "+", "true_label" + labelId, true, _offset);

			Copy(_result, "false");
			Jump("end_label" + labelId);

			Label("true_label" + labelId);
			Copy(_result, "true");

			Label("end_label" + labelId);
		}

		private static void IsPassable(string _level, string _offset, string _result)
		{
			string labelId = GenerateId();

			Compare(_level, " ", "true_label" + labelId, true, _offset);
			Compare(_level, "+", "true_label" + labelId, true, _offset);
			Compare(_level, "~", "true_label" + labelId, true, _offset);
			Compare(_level, "w", "true_label" + labelId, true, _offset);
			Compare(_level, "W", "true_label" + labelId, true, _offset);

			Copy(_result, "false");
			Jump("end_label" + labelId);

			Label("true_label" + labelId);
			Copy(_result, "true");

			Label("end_label" + labelId);
		}

		private static void IsGravity(string _level, string _offset, string _result)
		{
			string labelId = GenerateId();

			Compare(_level, " ", "true_label" + labelId, true, _offset);
			Compare(_level, "w", "true_label" + labelId, true, _offset);
			Compare(_level, "W", "true_label" + labelId, true, _offset);

			Copy(_result, "false");
			Jump("end_label" + labelId);

			Label("true_label" + labelId);
			Copy(_result, "true");

			Label("end_label" + labelId);
		}

		private static void IsDig(string _level, string _offset, string _result)
		{
			string labelId = GenerateId();

			Compare(_level, "#", "true_label" + labelId, true, _offset);

			Copy(_result, "false");
			Jump("end_label" + labelId);

			Label("true_label" + labelId);
			Copy(_result, "true");

			Label("end_label" + labelId);
		}

		private static void IsFall(string _level, string _offset, string _result)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("result_value" + id, 0);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);
			Copy(_result, "false");

			IsGravity(_level, _offset, "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "bottom_offset");
			IsStable(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "true", "end_label" + id);

			Copy(_result, "true");

			Label("end_label" + id);
		}

		private static void IsLeftMoving(string _level, string _offset, string _result)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("result_value" + id, 0);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);
			Copy(_result, "false");

			Compare("input_key", "left_move", "end_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "left_offset");
			IsPassable(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);

			Copy(_result, "true");

			Label("end_label" + id);
		}

		private static void IsRightMoving(string _level, string _offset, string _result)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("result_value" + id, 0);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);
			Copy(_result, "false");

			Compare("input_key", "right_move", "end_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "right_offset");
			IsPassable(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);

			Copy(_result, "true");

			Label("end_label" + id);
		}

		private static void IsTopMoving(string _level, string _offset, string _result)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("result_value" + id, 0);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);
			Copy(_result, "false");

			Compare("input_key", "top_move", "end_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "top_offset");
			IsPassable(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);

			IsFall(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "true", "end_label" + id);

			Copy(_result, "true");

			Label("end_label" + id);
		}

		private static void IsBottomMoving(string _level, string _offset, string _result)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("result_value" + id, 0);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);
			Copy(_result, "false");

			Compare("input_key", "bottom_move", "end_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "bottom_offset");
			IsPassable(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);

			Copy(_result, "true");

			Label("end_label" + id);
		}

		private static void UpdateChangeLevel()
		{
			string id = GenerateId();
			Compare("pers_fall", "true", "end_label" + id);

			ComputeDig("level", "pers_position");

			ComputeCreateBlock("level", "pers_position");

			Label("end_label" + id);
		}

		private static void ComputeDig(string _level, string _offset)
		{
			string id = GenerateId();
			Jump("left_dig_label" + id);
			ValueNumber("result_value" + id, 0);
			ValueNumber("offset_value" + id, 0);

			// копаем влево
			Label("left_dig_label" + id);
			Compare("input_key", "left_dig", "right_dig_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "left_offset");
			IsDig(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "right_dig_label" + id);

			Copy(_level, " ", "offset_value" + id);
			Jump("end_label" + id);

			// копаем вправо
			Label("right_dig_label" + id);
			Compare("input_key", "right_dig", "top_dig_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "right_offset");
			IsDig(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "top_dig_label" + id);

			Copy(_level, " ", "offset_value" + id);
			Jump("end_label" + id);

			// копаем вверх
			Label("top_dig_label" + id);
			Compare("input_key", "top_dig", "bottom_dig_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "top_offset");
			IsDig(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "bottom_dig_label" + id);

			Copy(_level, " ", "offset_value" + id);
			Jump("end_label" + id);

			// копаем вниз
			Label("bottom_dig_label" + id);
			Compare("input_key", "bottom_dig", "end_label" + id, false);

			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "bottom_offset");
			IsDig(_level, "offset_value" + id, "result_value" + id);
			Compare("result_value" + id, "false", "end_label" + id);

			Copy(_level, " ", "offset_value" + id);
			Label("end_label" + id);
		}

		private static void ComputeCreateBlock(string _level, string _offset)
		{
			string id = GenerateId();

			// блок 1
			Compare("input_key", "1", "key_2_label" + id, false);
			Copy(_level, " ", _offset);
			Jump("end_label" + id);

			// блок 2
			Label("key_2_label" + id);
			Compare("input_key", "2", "key_3_label" + id, false);
			Copy(_level, "#", _offset);
			Jump("end_label" + id);

			// блок 3
			Label("key_3_label" + id);
			Compare("input_key", "3", "key_4_label" + id, false);
			Copy(_level, "+", _offset);
			Jump("end_label" + id);

			// блок 4
			Label("key_4_label" + id);
			Compare("input_key", "4", "key_5_label" + id, false);
			Copy(_level, "~", _offset);
			Jump("end_label" + id);

			// блок 5
			Label("key_5_label" + id);
			Compare("input_key", "5", "end_label" + id, false);
			Copy(_level, "W", _offset);
			Label("end_label" + id);
		}

		private static void UpdateLevel()
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);

			// обрабатываем воду 1 проход
			Copy("offset_value" + id, "level_start");
			Label("loop_1_label" + id);

			ComputeWater1("level", "offset_value" + id, " ");
			ComputeWater1("level", "offset_value" + id, "w");
			ComputeWater1("level", "offset_value" + id, "W");

			Add("offset_value" + id, "one");
			Compare("offset_value" + id, "level_end", "start_2_label" + id);
			Jump("loop_1_label" + id);

			// обрабатываем воду 2 проход
			Label("start_2_label" + id);
			Copy("offset_value" + id, "level_start");
			Label("loop_2_label" + id);

			ReplaceBlock("level", "offset_value" + id, ".", "~");

			Add("offset_value" + id, "one");
			Compare("offset_value" + id, "level_end", "start_3_label" + id);
			Jump("loop_2_label" + id);

			// обрабатываем огонь 1 проход
			Label("start_3_label" + id);
			Copy("offset_value" + id, "level_start");
			Label("loop_3_label" + id);

			ComputeFire1("level", "offset_value" + id, "w");
			ComputeFire1("level", "offset_value" + id, "W");

			Add("offset_value" + id, "one");
			Compare("offset_value" + id, "level_end", "start_4_label" + id);
			Jump("loop_3_label" + id);

			// обрабатываем огонь 2 проход
			Label("start_4_label" + id);
			Copy("offset_value" + id, "level_start");
			Label("loop_4_label" + id);

			ReplaceBlock("level", "offset_value" + id, ".", "w");

			Add("offset_value" + id, "one");
			Compare("offset_value" + id, "level_end", "start_5_label" + id);
			Jump("loop_4_label" + id);

			// обрабатываем огонь 3 проход
			Label("start_5_label" + id);
			Copy("offset_value" + id, "level_start");
			Label("loop_5_label" + id);

			ReplaceBlock("level", "offset_value" + id, "W", ".");
			ReplaceBlock("level", "offset_value" + id, "w", "W");

			Add("offset_value" + id, "one");
			Compare("offset_value" + id, "level_end", "start_6_label" + id);
			Jump("loop_5_label" + id);

			// обрабатываем огонь 4 проход
			Label("start_6_label" + id);
			Copy("offset_value" + id, "level_start");
			Label("loop_6_label" + id);

			ReplaceBlock("level", "offset_value" + id, ".", "w");

			Add("offset_value" + id, "one");
			Compare("offset_value" + id, "level_end", "end_label" + id);
			Jump("loop_6_label" + id);

			Label("end_label" + id);
		}

		private static void ComputeWater1(string _level, string _offset, string _target)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);

			Compare(_level, _target, "end_label" + id, false, _offset);

			// вода слева
			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "left_offset");
			Compare(_level, "~", "right_label" + id, false, "offset_value" + id);
			Copy(_level, ".", _offset);

			// вода справа
			Label("right_label" + id);
			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "right_offset");
			Compare(_level, "~", "top_label" + id, false, "offset_value" + id);
			Copy(_level, ".", _offset);

			// вода сверху
			Label("top_label" + id);
			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "top_offset");
			Compare(_level, "~", "end_label" + id, false, "offset_value" + id);
			Copy(_level, ".", _offset);

			Label("end_label" + id);
		}

		private static void ComputeFire1(string _level, string _offset, string _fire)
		{
			string id = GenerateId();
			Jump("start_label" + id);
			ValueNumber("offset_value" + id, 0);

			Label("start_label" + id);

			Compare(_level, "+", "end_label" + id, false, _offset);

			// огонь слева
			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "left_offset");
			Compare(_level, _fire, "right_label" + id, false, "offset_value" + id);
			Copy(_level, ".", _offset);

			// огонь справа
			Label("right_label" + id);
			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "right_offset");
			Compare(_level, _fire, "bottom_label" + id, false, "offset_value" + id);
			Copy(_level, ".", _offset);

			// огонь снизу
			Label("bottom_label" + id);
			Copy("offset_value" + id, _offset);
			Add("offset_value" + id, "bottom_offset");
			Compare(_level, _fire, "end_label" + id, false, "offset_value" + id);
			Copy(_level, ".", _offset);

			Label("end_label" + id);
		}

		private static void ReplaceBlock(string _level, string _offset, string _target, string _source)
		{
			string id = GenerateId();

			Compare(_level, _target, "end_label" + id, false, _offset);
			Copy(_level, _source, _offset);

			Label("end_label" + id);
		}

		private static void Value(string _name, string _value) { m_compiler.Add(new Value(_name, _value)); }
		private static void ValueNumber(string _name, int _value = 0) { m_compiler.Add(new ValueNumber(_name, _value)); }
		private static void Jump(string _labelName) { m_compiler.Add(new Jump(_labelName)); }
		private static void Label(string _name) { m_compiler.Add(new Label(_name)); }
		private static void Print(string _valueName) { m_compiler.Add(new Print(_valueName)); }
		private static void Cursor(string _offsetValueName) { m_compiler.Add(new Cursor(_offsetValueName)); }
		private static void Add(string _targetValueName, string _sourceValueName) { m_compiler.Add(new Add(_targetValueName, _sourceValueName)); }
		private static void Copy(string _targetValueName, string _sourceValueName, string _targetOffsetValueName = "zero") { m_compiler.Add(new Copy(_targetValueName, _targetOffsetValueName, _sourceValueName)); }
		private static void ReadKey(string _targetValueName) { m_compiler.Add(new ReadKey(_targetValueName)); }
		private static void Compare(string _valueLeftName, string _valueRightName, string _label, bool _equal = true, string _valueLeftOffsetName = "zero") { m_compiler.Add(new Compare(_valueLeftName, _valueLeftOffsetName, _valueRightName, _label, _equal)); }
		private static string GenerateId() { m_id++; return "_" + m_id.ToString(); }

		private static Compiler m_compiler;
		private const int m_width = 80;
		private static int m_id = 0;
	}
}
